﻿//IQFeedHistorySource.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Algo.History.IQFeed
{
	using System;
	using System.Collections.Generic;
	using System.Globalization;
	using System.Linq;
	using System.Net;

	using Ecng.Collections;
	using Ecng.Common;

	using StockSharp.Algo.Storages;
	using StockSharp.BusinessEntities;

	/// <summary>
	/// Источник для скачивания исторических сделок через IQFeed API.
	/// </summary>
	public class IQFeedHistorySource : BaseHistorySource
	{
		private readonly IQFeedMarketDepthBuilder _depthBuilder = new IQFeedMarketDepthBuilder();
		private readonly SynchronizedDictionary<string, Security> _registeredMarketDepths = new SynchronizedDictionary<string, Security>();
		private readonly SynchronizedDictionary<string, Security> _registeredTrades = new SynchronizedDictionary<string, Security>();

		private IQFeedWrapper _level2Feed;
		private IQFeedWrapper _level1Feed;

		private IPEndPoint _adminAddress = IQFeedAddresses.DefaultAdminAddress;
		private IPEndPoint _lookupAddress = IQFeedAddresses.DefaultLookupAddress;
		private IPEndPoint _level2Address = IQFeedAddresses.DefaultLevel2Address;
		private IPEndPoint _level1Address = IQFeedAddresses.DefaultLevel1Address;

		/// <summary>
		/// Адрес для получения данных по Level1.
		/// </summary>
		public IPEndPoint Level1Address
		{
			get { return _level1Address; }
			set
			{
				if (value == null)
					throw new ArgumentNullException("value"); 
				
				_level1Address = value;
			}
		}

		/// <summary>
		/// Адрес для получения данных по Level2.
		/// </summary>
		public IPEndPoint Level2Address
		{
			get { return _level2Address; }
			set
			{
				if (value == null)
					throw new ArgumentNullException("value"); 
				
				_level2Address = value;
			}
		}

		/// <summary>
		/// Адрес для получения исторических данных.
		/// </summary>
		public IPEndPoint LookupAddress
		{
			get { return _lookupAddress; }
			set
			{
				if (value == null)
					throw new ArgumentNullException("value"); 
				
				_lookupAddress = value;
			}
		}

		/// <summary>
		/// Адрес для получения служебных данных.
		/// </summary>
		public IPEndPoint AdminAddress
		{
			get { return _adminAddress; }
			set
			{
				if (value == null)
					throw new ArgumentNullException("value");

				_adminAddress = value;
			}
		}

		/// <summary>
		/// Хранилище торговых объектов.
		/// </summary>
		public IEntityRegistry Registry { get; private set; }

		/// <summary>
		/// Список всех инструментов, зарегистрированных через <see cref="RegisterTrades"/>.
		/// </summary>
		public IEnumerable<Security> RegisteredTrades { get { return _registeredTrades.Values; } }

		/// <summary>
		/// Список всех инструментов, зарегистрированных через <see cref="RegisterMarketDepth"/>.
		/// </summary>
		public IEnumerable<Security> RegisteredMarketDepths { get { return _registeredMarketDepths.Values; } }

		/// <summary>
		/// Событие появления всех новых сделок.
		/// </summary>
		public event Action<IEnumerable<Trade>> NewTrades;

		/// <summary>
		/// Событие изменения стаканов с котировками.
		/// </summary>
		public event Action<IEnumerable<MarketDepth>> MarketDepthsChanged;

		/// <summary>
		/// Событие нового сообщения об ошибке.
		/// </summary>
		public event Action<Exception> Error;

		/// <summary>
		/// Создать <see cref="IQFeedHistorySource"/>.
		/// </summary>
		/// <param name="securityStorage">Хранилище информации об инструментах.</param>
		/// <param name="registry">Хранилище торговых объектов.</param>
		public IQFeedHistorySource(ISecurityStorage securityStorage, IEntityRegistry registry)
			: base(securityStorage)
		{
			if (registry == null)
				throw new ArgumentNullException("registry");

			Registry = registry;
		}

		private void RaiseError(Exception exception)
		{
			Error.SafeInvoke(exception);
		}

		private ExchangeBoard GetExchangeBoard(IQFeedListedMarketMessage market)
		{
			if (market == null)
				throw new ArgumentNullException("market");

			var board = Registry.ExchangeBoards.ReadById(market.Code);

			if (board == null)
			{
				board = ExchangeBoard.GetBoard(market.Code) ??
				        new ExchangeBoard
				        {
							Code = market.Code,
					        Exchange = new Exchange
					        {
								Name = market.Code,
								EngName = market.Name,
						        TimeZoneInfo = TimeZoneInfo.Utc,
					        },
				        };

				Registry.Exchanges.Save(board.Exchange);
				Registry.ExchangeBoards.Save(board);
			}

			return board;
		}

		private static SecurityTypes GetSecurityType(int value)
		{
			switch (value)
			{
				case 1:
					return SecurityTypes.Stock;
				case 8:
					return SecurityTypes.Future;
				case 9:
					return SecurityTypes.Option;
				default:
					throw new ArgumentException("Неизвестный тип инструмента '{0}'.".Put(value));
			}
		}

		/// <summary>
		/// Получить новые инструменты.
		/// </summary>
		/// <returns>Новые инструменты.</returns>
		public IEnumerable<Security> GetNewSecurities()
		{
			var newSecurities = new List<Security>();
			var securities = new Dictionary<string, Security>();
			var iqFeed = new IQFeedWrapper
			{
				Address = LookupAddress
			};

			var markets = iqFeed
				.SyncGet(iq => iq.RequestListedMarkets(), (IQFeedListedMarketMessage m) => m, RaiseError)
				.ToDictionary(i => i.Id);

			for (var c = 'A'; c <= 'Z'; c++)
			{
				var filter = c.ToString(CultureInfo.InvariantCulture);

				iqFeed
					.SyncDo(iq => iq.RequestSecurities(IQFeedSearchField.Symbol, filter, IQFeedFilterType.SecurityType, "1", "8"), (IQFeedSecurityMessage m) =>
					{
						if (securities.ContainsKey(m.Code))
							return;

						var board = GetExchangeBoard(markets[m.Market]);

						var security = new Security
						{
							Id = null,
							Code = m.Code,
							Name = m.Name,
							Class = string.Empty,
							ExchangeBoard = board,
							Type = GetSecurityType(m.SecurityType),
							ExtensionInfo = new Dictionary<object, object>(),
						};

						security.Id = SecurityIdGenerator.GenerateId(security.Code, security.Class, board);

						securities.Add(security.Code, security);
					}, RaiseError);
			}

			foreach (var security in securities.Values)
			{
				var s = SecurityStorage.LoadBy("Id", security.Id);

				if (s == null)
					newSecurities.Add(security);

				SecurityStorage.Save(security);
			}

			return newSecurities;
		}

		/// <summary>
		/// Получить исторические сделки.
		/// </summary>
		/// <param name="security">Инструмент, для которого необходимо получить все сделки. Если значение равно null, то возвращаются сделки по всем инструментам.</param>
		/// <param name="time">Время торговой сессии, для которой необходимо получить сделки.</param>
		/// <returns>Исторические сделки.</returns>
		public IEnumerable<Trade> GetTrades(Security security, DateTime time)
		{
			if (security == null)
				throw new ArgumentNullException("security");

			var trades = new List<Trade>();
			var iqFeed = new IQFeedWrapper { Address = LookupAddress };

			iqFeed.SyncDo(iq => iq.RequestTrades(security.Code, time.Date, time.Date.AddDays(1)), (IQFeedHistoryTradeMessage msg) => trades.Add(new Trade
			{
				Id = msg.Id,
				Security = security,
				Time = msg.Time,
				Price = msg.Price,
				Volume = msg.Volume,
			}), RaiseError);

			return trades;
		}

		/// <summary>
		/// Начать получение сделок для инструмента.
		/// </summary>
		/// <param name="security">Инструмент.</param>
		public void RegisterTrades(Security security)
		{
			if (security == null)
				throw new ArgumentNullException("security");

			_registeredTrades.Add(security.Code, security);

			if (_level1Feed == null)
			{
				_level1Feed = new IQFeedWrapper { Address = Level1Address };
				_level1Feed.ProcessReply += Level1FeedNewData;
				_level1Feed.ProcessDataError += RaiseError;
				_level1Feed.ConnectionError += err =>
				{
					RaiseError(err);

					_level1Feed.Disconnect();
					_level1Feed.Connect();
				};
				_level1Feed.Connect();
				_level1Feed.SetDefaultLevel1FieldSet();
			}

			_level1Feed.SubscribeSymbol(security.Code);
		}

		/// <summary>
		/// Остановить получение сделок для инструмента.
		/// </summary>
		/// <param name="security">Инструмент.</param>
		public void UnRegisterTrades(Security security)
		{
			if (security == null)
				throw new ArgumentNullException("security");

			_registeredTrades.Remove(security.Code);

			if (_level1Feed == null)
				return;

			_level1Feed.UnSubscribeSymbol(security.Code);

			if (_registeredTrades.Count == 0)
			{
				_level1Feed.Disconnect();
				_level1Feed = null;
			}
		}

		/// <summary>
		/// Начать получение стаканов для инструмента.
		/// </summary>
		/// <param name="security">Инструмент.</param>
		public void RegisterMarketDepth(Security security)
		{
			if (security == null)
				throw new ArgumentNullException("security");

			_registeredMarketDepths.Add(security.Code, security);

			if (_level2Feed == null)
			{
				_level2Feed = new IQFeedWrapper { Address = Level2Address };
				_level2Feed.ProcessReply += Level2FeedNewData;
				_level2Feed.ProcessDataError += RaiseError;
				_level2Feed.ConnectionError += err =>
				{
					RaiseError(err);

					_level2Feed.Disconnect();
					_level2Feed.Connect();
				};
				_level2Feed.Connect();
			}

			_level2Feed.SubscribeSymbol(security.Code);
		}

		/// <summary>
		/// Остановить получение стаканов для инструмента.
		/// </summary>
		/// <param name="security">Инструмент.</param>
		public void UnRegisterMarketDepth(Security security)
		{
			if (security == null)
				throw new ArgumentNullException("security");

			_registeredMarketDepths.Remove(security.Code);

			if (_level2Feed == null)
				return;

			_level2Feed.UnSubscribeSymbol(security.Code);

			if (_registeredMarketDepths.Count == 0)
			{
				_level2Feed.Disconnect();
				_level2Feed = null;
			}
		}

		private void Level2FeedNewData(IEnumerable<IQFeedMessage> messages)
		{
			if (messages == null)
				throw new ArgumentNullException("messages");

			foreach (var message in messages)
			{
				System.Diagnostics.Debug.WriteLine(message);

				if (message.Type == IQFeedMessageType.Quote)
				{
					var msg = (IQFeedQuoteMessage)message;
					var security = _registeredMarketDepths[msg.Symbol];

					_depthBuilder.Update(security, msg);
				}
			}

			var changes = _depthBuilder.GetChanges();
			if (changes.Length > 0)
				MarketDepthsChanged.SafeInvoke(changes);
		}

		private void Level1FeedNewData(IEnumerable<IQFeedMessage> messages)
		{
			if (messages == null)
				throw new ArgumentNullException("messages");

			System.Diagnostics.Debug.WriteLine(messages.Select(m => m.ToString()).Join(Environment.NewLine));

			var trades = messages
				.OfType<IQFeedTradeMessage>()
				.Select(msg => new Trade
				{
					Id = msg.Id,
					Security = _registeredTrades[msg.Symbol],
					Time = msg.Time,
					Price = msg.Price,
					Volume = msg.Volume,
				})
				.ToArray();

			if (trades.Length > 0)
				NewTrades.SafeInvoke(trades);
		}
	}
}
